package com.zlsy.es.dianping.baseexception;

import cn.hutool.core.util.StrUtil;
import com.zlsy.es.dianping.base.R;
import com.zlsy.es.dianping.constan.ErrorStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @author zhouliang
 * @date 2020/2/29 0:57
 */
@Slf4j
@RestControllerAdvice
public abstract class BaseExceptionHandler {

    @ExceptionHandler(value = BizException.class)
    public Object businessExceptionHandler(BizException exception) {
        Throwable throwable = findRootExceptionThrowable(exception);

        if (throwable != null && !(throwable instanceof BizException)) {
            log.error("业务异常: {}, {}", exception.getErrMsg(), exception);
        } else {
            log.warn("业务异常: {}, {}", exception.getErrMsg(), exception.getStackTrace().length > 0 ? exception.getStackTrace()[0] : "");
        }

        return R.error(null != exception.getErrCode() ? exception.getErrCode() : ErrorStatusEnum.UNKNOWN_ERROR.getErrCode(), exception.getErrMsg());
    }

    private Throwable findRootExceptionThrowable(Exception e) {
        Throwable rootCause = e;
        while (rootCause.getCause() != null) {
            rootCause = rootCause.getCause();
        }
        return rootCause;
    }

    /**
     * spring 请求参数绑定异常处理
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public Object constraintViolationException(ConstraintViolationException exception) {
        List<String> invalidArguments = new ArrayList<>();

        Set<ConstraintViolation<?>> errors = exception.getConstraintViolations();
        for (ConstraintViolation<?> violation : errors) {
            String field = ((PathImpl) violation.getPropertyPath()).getLeafNode().toString();
            invalidArguments.add(field + ":" + violation.getMessage());
        }

        String errorStr = StrUtil.join(",", invalidArguments);
        return R.error(ErrorStatusEnum.INVALID_ARGUMENTS.getErrCode(), errorStr);
    }

    /**
     * validtor 参数绑定异常处理
     *
     * @param exception
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = BindException.class)
    public Object methodArgumentNotValidHandler(BindException exception) {
        String errorStr = null;
        for (ObjectError error : exception.getBindingResult().getAllErrors()) {
            errorStr = error.getDefaultMessage();
            break;
        }
        return R.error(ErrorStatusEnum.INVALID_ARGUMENTS.getErrCode(), errorStr);
    }

    @ResponseBody
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public Object httpRequestMethodNotValidHandler(HttpRequestMethodNotSupportedException exception) {
        return R.error(ErrorStatusEnum.FORBIDDEN_REQUEST.getErrCode(), exception.getMessage());
    }

    /**
     * json 格式接口加参数校验的统一异常处理
     *
     * @param exception
     * @return
     */
    @ResponseBody
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public Object methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException exception) {
        BindingResult bindingResult = exception.getBindingResult();
        StringBuffer sb = new StringBuffer();
        List<ObjectError> allErrors = bindingResult.getAllErrors();
        allErrors.forEach(error -> {
            String defaultMessage = error.getDefaultMessage();
            sb.append(defaultMessage);
        });
        return R.error(ErrorStatusEnum.INVALID_ARGUMENTS.getErrCode(), sb.toString());
    }

    @ResponseBody
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public Object httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException ex) {
        Throwable throwable = findRootExceptionThrowable(ex);
        BizException bizException = null;
        try {
            bizException = (BizException) throwable;
        } catch (Exception e) {
            log.error("业务异常:{}", e.getMessage());
        }
        return R.error(null != bizException ? bizException.getErrCode() : ErrorStatusEnum.BUSINESS_ERROR.getErrCode(), null != bizException ? bizException.getErrMsg() : ex.getMessage());
    }

    @ResponseBody
    @ExceptionHandler(value = DuplicateKeyException.class)
    public Object dataIntegrityViolationException(DuplicateKeyException exception) {
        log.error("数据库异常", exception);
        return R.error(ErrorStatusEnum.INVALID_ARGUMENTS.getErrCode(), "主键冲突或违反唯一约束");
    }

    /**
     * 未知异常的统一的处理
     *
     * @param exception
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Object unknownExceptionHandler(Exception exception) {
        log.error("系统异常", exception);
        return gerUnkownExceptionMessage(exception);
    }

    /**
     * 针对未知异常类获取错误内容
     *
     * @param exception
     * @return
     */
    protected R gerUnkownExceptionMessage(Exception exception) {
        String message = exception.getMessage();
        return R.error(ErrorStatusEnum.UNKNOWN_ERROR.getErrCode(), message);
    }
}
